package eiisan.service.task.base;

import com.fasterxml.jackson.core.JsonProcessingException;
import eiisan.config.aop.exception.annotation.CatchAndReturn;
import eiisan.config.aop.validation.annotation.Check;
import eiisan.config.task.TaskProperties;
import eiisan.constant.enums.task.StatusEnum;
import eiisan.constant.errors.code.ErrorCode;
import eiisan.constant.errors.exception.ServiceException;
import eiisan.constant.settings.DefStr;
import eiisan.dao.domain.task.Task;
import eiisan.dao.repository.task.TaskRepository;
import eiisan.service.task.dto.AvailableTaskDTO;
import eiisan.util.common.StrUtil;
import eiisan.util.common.StreamUtil;
import eiisan.util.json.JSONUtil;
import eiisan.util.model.response.PageData;
import eiisan.util.model.response.Result;
import eiisan.util.spring.ContextUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.persistence.criteria.Predicate;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.stream.Collectors;

@Slf4j
@Configuration
@EnableScheduling
@DependsOn("jsonUtil")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class BaseTaskService {

    @Autowired
    private ThreadPoolTaskScheduler scheduler;

    @Autowired
    private TaskProperties properties;

    @Autowired
    private TaskRepository taskRepository;

    /**
     * 可用任务信息
     */
    private List<AvailableTaskDTO> taskServiceInfos;

    /**
     * 当前任务
     */
    private Map<Long, ScheduledFuture<?>> taskMap = new ConcurrentHashMap<>();

    /**
     * 当前任务信息
     */
    private Map<Long, Task> taskInfoMap = new ConcurrentHashMap<>();

    /**
     * 初始化<br>
     */
    @PostConstruct
    private void init() {
        try {
            taskServiceInfos = ContextUtil.gets(TaskService.class).entrySet().stream().map((entry) -> {
                AvailableTaskDTO info = new AvailableTaskDTO();
                info.setName(entry.getKey());
                info.setDescription(entry.getValue().getDescription());
                info.setPermitForceInterrupt(!canForcelyIntterrupt(entry.getValue()));
                return info;
            }).collect(Collectors.toList());
             List<String> targets = taskServiceInfos.stream().map(AvailableTaskDTO::getName)
                    .collect(Collectors.toList());
            taskRepository.deleteAllByTargetNotIn(targets);
            Pageable pageable = PageRequest.of(0, properties.getMaxSize(), Sort.by(Sort.Direction.ASC, "id"));
            List<Task> tasks = taskRepository.findAll((root, query, cb) -> {
                Predicate p1 = cb.equal(root.get("status"), StatusEnum.ACTIVE.getVal());
                Predicate p2 = cb.isNotNull(root.get("target"));
                Predicate p = cb.and(p1, p2);
                return p;
            }, pageable).getContent();
            tasks.parallelStream().forEach((task) -> {
                Runnable runnable = null;
                CronTrigger trigger = null;
                try {
                    runnable = getRunnable(task.getTarget());
                } catch (ClassNotFoundException e) {
                    log.error("init", e);
                    log.warn("初始化任务——类型初始化失败!类型名:{}", task.getTarget());
                    return;
                }
                try {
                    trigger = new CronTrigger(task.getCronExp());
                } catch (IllegalArgumentException e) {
                    log.error("init", e);
                    log.warn("初始化任务——时间表达式错误!表达式:{}", task.getCronExp());
                    return;
                }
                if (runnable != null && trigger != null) {
                    taskMap.put(task.getId(), scheduler.schedule(runnable, trigger));
                    taskInfoMap.put(task.getId(), task);
                    try {
                        log.info("第{}号任务启动,任务详情:{}", task.getId(), JSONUtil.string(task));
                    } catch (JsonProcessingException e) {
                        return;
                    }
                }
            });
        } catch (Exception e) {
            log.error("初始化任务错误!应用仍将继续启动...", e);
        }
    }

    /**
     * 判断是否为可以强行中断的接口
     */
    private boolean canForcelyIntterrupt(Runnable runnable) {
        return isTransactional(runnable);
    }

    /**
     * 判断是否为带有事务的接口
     */
    private boolean isTransactional(Runnable runnable) {
        try {
            return runnable.getClass().getAnnotation(Transactional.class) != null || runnable.getClass().getMethod(
                    "run").getAnnotation(Transactional.class) != null;
        } catch (NoSuchMethodException e) {
            return false;
        } catch (SecurityException e) {
            return false;
        }
    }

    /**
     * 获取可用的定时任务实例用于开启定时任务
     */
    private Runnable getRunnable(String className) throws ClassNotFoundException {
        return (Runnable) ContextUtil.get(Class.forName(StrUtil.concat(properties.getPrefix(), DefStr.POINT_SEPERATOR,
                className)));
    }

    /**
     * 是否可用
     */
    private boolean isAvailable(String target) {
        return taskServiceInfos.stream().filter((iter) -> {
            return iter.getName().equals(target);
        }).count() != 0;
    }

    /**
     * 获得可用的任务
     */
    @Check
    @CatchAndReturn
    public Result<PageData<AvailableTaskDTO>> getAvailableTasks(@Valid @NotNull Pageable param) {
        List<AvailableTaskDTO> infos = StreamUtil.startPage(taskServiceInfos.stream(), param).collect(Collectors.toList());
        return Result.page(param.getPageNumber(), param.getPageSize(), taskServiceInfos.size(), infos);
    }

    /**
     * 获取正在运行的任务信息
     */
    public Result<PageData<Task>> getRunningTasksInfo(@Valid @NotNull Pageable param) {
        try {
            List<Task> tasks = StreamUtil.startPage(taskInfoMap.values().stream(), param).collect(Collectors.toList());
            return Result.page(param.getPageNumber(), param.getPageSize(), taskInfoMap.values().size(), tasks);
        } catch (Exception e) {
            log.error("[getRunningTasksInfo]", e);
            return Result.errorServe();
        }
    }

    /**
     * 获取保存的任务信息
     */
    public Result<PageData<Task>> getTasks(@Valid @NotNull Pageable param) {
        try {
            return Result.page(taskRepository.findAll(param));
        } catch (Exception e) {
            log.error("[getRunningTasks]", e);
            return Result.errorServe();
        }
    }

    /**
     * 强制取消当前所有任务
     */
    public Result cancelAllTasks() {
        try {
            int total = taskMap.size();
            int alreadyCanceled = 0;
            for (Map.Entry<Long, ScheduledFuture<?>> entry : taskMap.entrySet()) {
                if (entry.getValue().cancel(true)) {
                    taskMap.remove(entry.getKey());
                    taskInfoMap.remove(entry.getKey());
                    ++alreadyCanceled;
                }
            }
            return Result.success(StrUtil.concat("共计", Integer.valueOf(total).toString(), "个任务正在执行,已取消", Integer
                    .valueOf(alreadyCanceled).toString(), "个。"));
        } catch (Exception e) {
            log.error("[cancelAllTasks]", e);
            return Result.errorServe();
        }
    }

    /**
     * 添加任务
     */
    @Transactional
    public Result addTask(@Valid @NotNull Task param) {
        try {
            if (isAvailable(param.getTarget())) {
                return Result.fail("非法的类型!");
            }
            Runnable runnable;
            CronTrigger trigger;
            try {
                runnable = getRunnable(param.getTarget());
                trigger = new CronTrigger(param.getCronExp());
            } catch (ClassNotFoundException e) {
                return Result.fail("类型初始化失败!");
            } catch (IllegalArgumentException e) {
                return Result.fail("时间表达式错误!");
            }
            if (taskRepository.count() >= properties.getMaxSize()) {
                return Result.fail("已达可用任务最大数量!");
            }
            param.setId(null);
            param.setCreateTime(new Date());
            Task task = taskRepository.save(param);
            if (task.getStatus() == StatusEnum.ACTIVE.getVal()) {
                taskMap.put(task.getId(), scheduler.schedule(runnable, trigger));
                taskInfoMap.put(task.getId(), task);
            }
            return Result.success(task.getId());
        } catch (Exception e) {
            log.error("[addJob]", e);
            throw new ServiceException(ErrorCode.TASK_SERVICE_ERROR);
        }
    }

    /**
     * 中止并删除任务
     */
    public Result deleteTask(@NotNull Long id) {
        try {
            Task task = taskRepository.getOne(id);
            if (task == null) {
                return Result.fail("未找到该任务!");
            }
            ScheduledFuture<?> future = taskMap.get(id);
            if (future != null) {
                Runnable runnable;
                try {
                    runnable = getRunnable(task.getTarget());
                } catch (ClassNotFoundException e) {
                    return Result.fail("类型初始化失败!");
                }
                boolean mayInterruptIfRunning = canForcelyIntterrupt(runnable);
                if (!future.cancel(mayInterruptIfRunning)) {
                    if (mayInterruptIfRunning) {
                        return Result.fail("该任务正在执行,且不能强行中止,因此暂不能删除!");
                    } else {
                        return Result.fail("该任务正在执行,且中止失败,因此暂不能删除!");
                    }
                }
            }
            taskRepository.deleteById(id);
            taskMap.remove(id);
            taskInfoMap.remove(id);
            return Result.success();
        } catch (Exception e) {
            log.error("[removeJob]", e);
            throw new ServiceException(ErrorCode.TASK_SERVICE_ERROR);
        }
    }

    /**
     * 中止任务
     */
    public Result cancelTask(@NotNull Long id) {
        try {
            ScheduledFuture<?> future = taskMap.get(id);
            if (future == null) {
                return Result.fail("未找到该任务!");
            }
            if (!future.cancel(true)) {
                return Result.fail("强行取消任务失败!");
            }
            taskMap.remove(id);
            taskInfoMap.remove(id);
            return Result.success();
        } catch (Exception e) {
            log.error("[removeJob]", e);
            return Result.errorServe();
        }
    }

    /**
     * 更改任务活跃状态
     */
    @Transactional
    public Result changeStatus(@NotNull Long id, Byte status) {
        try {
            Task task = taskRepository.getOne(id);
            if (task == null) {
                return Result.fail("未找到该任务!");
            }
            task.setStatus(status);
            taskRepository.save(task);
            return Result.success();
        } catch (Exception e) {
            log.error("[removeJob]", e);
            throw new ServiceException(ErrorCode.TASK_SERVICE_ERROR);
        }
    }

}
